package com.andrewchatham.pony;

import java.util.ArrayList;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.util.Log;

import com.andrewchatham.SynctasticProto.AnnounceResponse;

class Server implements OnSharedPreferenceChangeListener, Discoverer.Receiver {
  private final static String TAG = "SyncServer";
  private String mHostAddr;
  private int mPort;
  private boolean mKnown;
  private boolean mDiscovered;
  public final static int BAD_PORT = -1;
  private Context mContext;
  private ErrorHandler mError;
  private String mUUID;
  private PairingHandler mPairingHandler;

  /**
   * If the user has run a scan before, this is set to the name of their
   * preferred server.
   */
  private String mAutoName;

  public Server(Context context, ErrorHandler error, NetworkInfo wifiInfo, SharedPreferences prefs) {
    mError = error;
    mContext = context;
    setPreferences(prefs);
    prefs.registerOnSharedPreferenceChangeListener(this);
    mDiscovered = false;

    // Do we need to discover the sever with mAutoName?
    if (mAutoName != null && mAutoName.length() > 0) {
      // Only send a discovery request if the user has done a scan before and
      // picked a server.
      new Discoverer((WifiManager) context.getSystemService(Context.WIFI_SERVICE), this).start();
      mKnown = false;
    } else {
      mKnown = true;
    }
  }

  public void setHostPort(String host, int port) {
    mHostAddr = host;
    mPort = port;
  }

  /**
   * If the host needs to be discovered by broadcast, this blocks until the answer is ready.
   * 
   * @return Whether the host was discovered.
   */
  public synchronized boolean waitForDiscovery() {
    if (mKnown)
      return mKnown;
    while (!mDiscovered)  {
      try {
        wait();
      } catch (InterruptedException e) {
        return false;
      }
    }
    return mKnown;
  }

  String host() {
    return mHostAddr;
  }

  int port() {
    return mPort;
  }


  /**
   * Return the HTTP URL for the given query, adding the UUID parameter.
   * @param path Path to request. Eg, "/playlists".
   * @param query Query components to add. May be null. Eg, "file=XX"
   * @return The full URL.
   */
  public String formatRequest(String path, String query) {
    if (query == null)
      query = "";
    return String.format("http://%s:%d%s?%s&uuid=%s", mHostAddr, mPort, path, query, mUUID); 
  }

  /**
   * Set the state of the Server based on prefs. This should be called
   * after every preference change or when starting up.
   * 
   * @param prefs
   */
  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
    setPreferences(prefs);
  }

  private void setPreferences(SharedPreferences prefs) {
    String host = prefs.getString(mContext.getString(R.string.host_key), null);
    // Only set the host if it was non-empty. We don't want to overwrite the
    // auto-discovered values.
    if (host != null && host.length() > 0) {
      int port;
      try {
        port = Integer.parseInt(prefs.getString(mContext.getString(R.string.port_key),
        "8800"));
      } catch (NumberFormatException e) {
        port = Server.BAD_PORT;
        mError.ShowError("Port must be a number");
      }
      setHostPort(host, port);
    }
    String oldAuto = mAutoName;
    mAutoName = prefs.getString(mContext.getString(R.string.discovered_name_key), null);
    Log.d(TAG, "Setting name to " + mAutoName);
    mUUID = prefs.getString(mContext.getString(R.string.uuid_key), null);
    if (mPairingHandler != null && mUUID != null)
      mPairingHandler.handlePairingResult(checkPairing());
    if (mAutoName != null && !mAutoName.equals(oldAuto))
      refreshDiscovery();
  }
  
  private void refreshDiscovery() {
    new Discoverer((WifiManager) mContext.getSystemService(Context.WIFI_SERVICE), this).start();
    mKnown = false;
  }
  
  interface PairingHandler {
    public void handlePairingResult(boolean paired);
  }

  /**
   * Set a callback for checking on pairing results. When the preferences change, we'll check the server to see whether we're paired.
   * 
   * @param handler
   */
  public void setPairingHandler(PairingHandler handler) {
    mPairingHandler = handler;
  }


  /**
   * Check whether our UUID is paired with the server.
   * @return Whether the UUID was accepted.
   */
  public boolean checkPairing() {
    HTTPRequest req = new HTTPRequest(formatRequest("/playlists", null));
    req.run();
    return req.success();
  }

  /**
   * Called when the discovery request we sent in onCreate finishes. If we find
   * a server matching mAutoName, we use that. Wakes waitForDiscovery().
   * 
   * @param servers
   *          list of discovered servers
   */
  public synchronized void addAnnouncedServers(ArrayList<AnnounceResponse> servers) {
    mDiscovered = true;
    if (mHostAddr != null && mHostAddr.length() > 0) {
      Log.d(TAG, "Skipping announced servers. Set manually");
      notifyAll();
      return;
    }

    for (AnnounceResponse server : servers) {
      if (server.getHostname().equals(mAutoName)) {
        if (!server.hasAddress()) {
          mError.ShowError(String.format("Found '%s' but looks broken", server
              .getHostname()));
          continue;
        } else {
          // Yay, found it and it works
          Log.d(TAG, "Setting host from discovery");
          setHostPort(server.getAddress(), server.getPort());
          mKnown = true;
          notifyAll();
          return;
        }
      }
    }

    notifyAll();
    mError.ShowError(String.format("Could not find preferred server '%s'",
        mAutoName));
  }

}